﻿//***********************************************************************
// Assembly         : IGolovkine.AoA.Tcp.Channels.Http
// Author           : Ilia
// Created          : 06-11-2011
//
// Last Modified By : Ilia
// Last Modified On : 06-11-2011
// Description      : 
//
// Copyright        : (c) Microsoft. All rights reserved.
//***********************************************************************
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using IGolovkine.AoA.Compression;
using IGolovkine.AoA.Diagnostics;

namespace IGolovkine.AoA.Tcp.Channels.Http
{
    public class HttpEnvelopeExchangeStrategy
    {
        private const string HttpEnvelopeCompressionHeaderKey = "HttpEnvelopeCompression";
        private readonly ILogger _logger;

        private readonly static Dictionary<string, ICompressionAlgorithm> CompressionAlgos = new Dictionary<string, ICompressionAlgorithm> 
            { 
                { DeflateCompression.Name, new DeflateCompression() }, 
                { NoCompression.Name, new NoCompression() } 
            };

        public HttpEnvelopeExchangeStrategy()
        {
            _logger = ApplicationContext.LogManager.GetClassLogger(this);
        }

        public string Send(HttpEnvelope envelope, HttpWebRequest request)
        {
            return this.InternalSend(envelope, request.Headers, request.GetRequestStream, l=> request.ContentLength = l);
        }

        public string Send(HttpEnvelope envelope, HttpListenerResponse request)
        {
            return this.InternalSend(envelope, request.Headers, ()=> request.OutputStream, l => request.ContentLength64 = l);
        }

        private string InternalSend(HttpEnvelope envelope, NameValueCollection headers, Func<Stream> stream, Action<long> setContentLength)
        {
            
            if (envelope.GetMessages().Length > 0)
            {
                byte[] data = this.Serialize(envelope);
                byte[] compressed = null;

                ICompressionAlgorithm algoUsed = null;

                foreach (ICompressionAlgorithm algo in CompressionAlgos.Values)
                {
                    byte[] pending = algo.Compress(data);

                    if (compressed == null || pending.Length < compressed.Length)
                    {
                        compressed = pending;
                        algoUsed = algo;
                    }
                }

                headers.Add(HttpEnvelopeCompressionHeaderKey, algoUsed.Name);
                setContentLength(compressed.Length);
                stream().Write(compressed, 0, compressed.Length);
                return algoUsed.Name;
            }
            else
            {
                setContentLength(0);
                return NoCompression.Name;
            }
        }

        public HttpEnvelope Receive(HttpListenerRequest request)
        {
            if (request.ContentLength64 > 0)
            {
                return this.InternalReceive(request.Headers, request.InputStream);
            }
            else
            {
                return HttpEnvelope.Empty;
            }
        }

        public HttpEnvelope Receive(WebResponse response)
        {
            if (response.ContentLength > 0)
            {
                return this.InternalReceive(response.Headers, response.GetResponseStream());
            }
            else
            {
                return HttpEnvelope.Empty;
            }
        }

        private HttpEnvelope InternalReceive(NameValueCollection headers, Stream stream)
        {
            string algoName = headers[HttpEnvelopeCompressionHeaderKey];
            if (algoName == null)
            {
                throw new InvalidOperationException("Failed to retrieve compression method from the Http Header");
            }

            byte[] compressed = stream.ReadAllBytes(1024);
            ICompressionAlgorithm algo =  CompressionAlgos[algoName];
            byte[] decompressed = algo.Decompress(compressed);
            return this.Deserialize(decompressed);
        }

        private byte[] Serialize(HttpEnvelope envelope)
        {
            using (MemoryStream mem = new MemoryStream())
            {
                var writer = new BinarySerialization.SlimBinaryWriter(mem);
                writer.WriteObjectArray(envelope.GetMessages());
                return mem.ToArray();
            }
        }

        private HttpEnvelope Deserialize(byte[] data)
        {
            using(MemoryStream mem = new MemoryStream(data))
            {
                var reader = new BinarySerialization.SlimBinaryReader(mem);
                HttpEnvelope result = new HttpEnvelope(reader.ReadObjectArray().OfType<Message>().ToArray());
                return result;
            }
        }
    }
}
